package phototransfer.model.filetransfer;

import java.io.File;
import java.nio.ByteBuffer;

public class DataPacket {
   private boolean isCorrupt;
   private boolean isFirstPacket;
   private boolean isLastPacket;
   private String filename;
   private int ackPort;
   private int sequenceNumber;
   private long fileLength;
   private byte[] data;
   public static final int HEADER_SIZE = 9;
   static final int SEQUENCE_NO_INDEX = 0;
   static final int CHECKSUM_INDEX = 4;
   static final int PACKET_LENGTH_INDEX = 6;
   static final int FLAG_INDEX = 8;
   static final int DATA_INDEX = 9;
   static final byte FIRST_PACKET_FLAG = 1;
   static final byte LAST_PACKET_FLAG = 2;

   private DataPacket() {
      this.isCorrupt = false;
      this.isFirstPacket = false;
      this.isLastPacket = false;
      this.data = null;
      this.filename = null;
      this.sequenceNumber = -1;
      this.ackPort = -1;
   }

   public long getFileLength() {
      return this.fileLength;
   }

   public DataPacket(byte[] data) {
      this();
      ByteBuffer buffer = ByteBuffer.wrap(data);
      if (buffer.capacity() < 9) {
         this.isCorrupt = true;
      } else if (!isChecksumValid(buffer.array())) {
         this.isCorrupt = true;
      } else {
         byte flags = buffer.get(8);
         short packetLength = buffer.getShort(6);
         this.sequenceNumber = buffer.getInt(0);
         if ((flags & 1) > 0) {
            this.isFirstPacket = true;
         }

         if ((flags & 2) > 0) {
            this.isLastPacket = true;
         }

         if (buffer.capacity() < packetLength) {
            this.isCorrupt = true;
         } else {
            this.data = new byte[packetLength - 9];
            buffer.position(9);
            if (this.isFirstPacket) {
               if (buffer.remaining() < 4) {
                  this.isCorrupt = true;
                  return;
               }

               this.ackPort = buffer.getInt();
               int filenameSize = buffer.getInt();
               byte[] filenameBytes = new byte[filenameSize];
               buffer.get(filenameBytes);
               this.filename = new String(filenameBytes);
               this.fileLength = buffer.getLong();
            } else {
               buffer.get(this.data);
            }

         }
      }
   }

   public DataPacket(File file, int ackPort) {
      this();
      this.isFirstPacket = true;
      this.filename = String.format("%s", file.getName());
      this.sequenceNumber = 0;
      byte[] filenameBytes = this.filename.getBytes();
      int dataLength = filenameBytes.length + 4 + 4 + 8;
      ByteBuffer buffer = ByteBuffer.allocate(dataLength);
      buffer.putInt(ackPort);
      buffer.putInt(filenameBytes.length);
      buffer.put(filenameBytes);
      buffer.putLong(file.length());
      this.data = buffer.array();
   }

   public DataPacket(byte[] data, int dataLength, boolean isLastPacket) {
      this();
      this.isLastPacket = isLastPacket;
      this.data = new byte[dataLength];
      if (data != null) {
         System.arraycopy(data, 0, this.data, 0, dataLength);
      }

   }

   public boolean isLastPacket() {
      return this.isLastPacket;
   }

   public boolean isInitPacket() {
      return this.isFirstPacket;
   }

   public boolean isCorrupt() {
      return this.isCorrupt;
   }

   public String getFilename() {
      return this.filename;
   }

   public int getAckPort() {
      return this.ackPort;
   }

   public int getSequenceNumber() {
      return this.sequenceNumber;
   }

   public void setSequenceNumber(int value) {
      this.sequenceNumber = value;
   }

   public byte[] getData() {
      return this.data;
   }

   public byte[] serialize() {
      int packetSize = this.data.length + 9;
      ByteBuffer buffer = ByteBuffer.allocate(packetSize);
      byte flags = 0;
      if (this.isFirstPacket) {
         flags = (byte)(flags | 1);
      }

      if (this.isLastPacket) {
         flags = (byte)(flags | 2);
      }

      buffer.putInt(0, this.sequenceNumber);
      buffer.putShort(4, (short)0);
      buffer.putShort(6, (short)packetSize);
      buffer.put(8, flags);
      buffer.position(9);
      buffer.put(this.data);
      short checksum = (short)((int)calculateChecksum(buffer.array()));
      buffer.putShort(4, checksum);
      return buffer.array();
   }

   private static boolean isChecksumValid(byte[] buffer) {
      return calculateChecksum(buffer) == 0L;
   }

   private static long calculateChecksum(byte[] buffer) {
      long sum = 0L;
      int length = buffer.length;

      int i;
      for(i = 0; length > 1; length -= 2) {
         long data = (long)(buffer[i] << 8 & '\uff00' | buffer[i + 1] & 255);
         sum += data;
         if ((sum & -65536L) > 0L) {
            sum &= 65535L;
            ++sum;
         }

         i += 2;
      }

      if (length > 0) {
         sum += (long)(buffer[i] << 8 & '\uff00');
         if ((sum & -65536L) > 0L) {
            sum &= 65535L;
            ++sum;
         }
      }

      sum = ~sum;
      sum &= 65535L;
      return sum;
   }
}
